from collections import deque
from utils.interval import Interval
from utils.prettyrepr import prettify_class

from amaze.entities.entity import Entity
from amaze.directions import CHAR, RIGHT, FLOAT_DX, FLOAT_DY


class MovableEntity(Entity):
    __slots__ = ("speed", "direction", "path", "x", "y")
    default_speed = 1.0  # tiles per time unit

    def __init__(self, id, tile=None, speed=None):
        Entity.__init__(self, id, tile)
        if speed is None:
            speed = self.default_speed
        self.speed = speed      # speed at which the entity moves
        self.direction = RIGHT  # current direction
        self.path = Path(self)  # contains a list of directions we will use for movement
        self.x = 0.0            # current x (float)
        self.y = 0.0            # current y (float)
        self.active = True      # movable entities are always active

    def __info__(self):
        return ("%s, x=%.01f, y=%.01f, s=%.01f, d=%s, m=%s" %
                (Entity.__info__(self), self.x, self.y, self.speed,
                 CHAR[self.direction], self.moving))

    @property
    def coords(self):
        return (self.x, self.y)

    @property
    def moving(self):
        return len(self.path) > 0

    def move(self, dt):
        if not self.moving:
            return
        distance = self.speed * dt
        cx, cy = self.path.tiles[0].center
        x0, y0 = self.coords
        x1 = x0 + FLOAT_DX[self.direction] * distance
        y1 = y0 + FLOAT_DY[self.direction] * distance
        # check if we reached the center of the next tile in our path and popleft() from path
        if cx in Interval(x0, x1, auto_sort=True) and cy in Interval(y0, y1, auto_sort=True):
            # print self.id, "->", cx, cy, "(reached tile center)"
            self.x = cx
            self.y = cy
            self.path.popleft()
            # if path is not empty and we haven't spent the whole 'dt', continue moving
            if len(self.path) > 0:
                traveled_distance = abs(cx - x0) + abs(cy - y0)
                elapsed_time = traveled_distance / self.speed
                remaining_time = dt - elapsed_time
                if remaining_time > 0.0:
                    self.move(remaining_time)
            return
        # print self.id, "->", x1, y1,
        # check if we moved into a different tile
        if int(x1) != int(x0) or int(y1) != int(y0):
            self.tile = self.maze[int(x1), int(y1)]
        #     print "(changed tile to %s)" % self.tile
        # else:
        #     print
        # and finally update our position
        self.x = x1
        self.y = y1

    def init(self):
        self.x = self.tile.cx
        self.y = self.tile.cy

    def step(self, dt):
        self.move(dt)


@prettify_class
class Path(object):
    """A path is a sequence of directions that an entity will take in the maze."""
    __slots__ = ("entity", "directions", "tiles")

    def __init__(self, entity):
        self.entity = entity
        self.directions = deque()
        self.tiles = deque()

    def __info__(self):
        return "+".join(CHAR[direction] for direction in self.directions)

    def __len__(self):
        return len(self.directions)

    def append(self, direction, repeat=1):
        if len(self.directions) == 0:
            self.entity.direction = direction
            tile = self.entity.tile
        else:
            tile = self.tiles[-1]
        for _ in xrange(repeat):
            tile = tile.neighbor_tile(direction=direction)
            self.directions.append(direction)
            self.tiles.append(tile)

    def popleft(self):
        if len(self.directions) > 1:
            print "%s changing direction to %s" % (self.entity.id, CHAR[self.directions[1]])
            self.entity.direction = self.directions[1]
        self.tiles.popleft()
        return self.directions.popleft()

    def clear(self):
        self.directions.clear()
        self.tiles.clear()
